#include "glCanvas.h"

// Included files for OpenGL Rendering
#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#endif

#include "glCanvas.h"
#include "mesh.h"
#include "argparser.h"
#include "camera.h"
#include "tgaLoad.h"


// ========================================================
// static variables of GLCanvas class

int GLCanvas::mouseButton;
int GLCanvas::mouseX;
int GLCanvas::mouseY;
int GLCanvas::display_list_index;
ArgParser* GLCanvas::args;
Mesh* GLCanvas::mesh;
Camera* GLCanvas::camera;
bool GLCanvas::controlPressed;
GLuint texture_id[3];


int GLCanvas::curmouseX;
int GLCanvas::curmouseY;
bool bladestate = 0;
BoundingBox initial_box;


// ========================================================

void GLCanvas::InitLight() {
  // Set the last component of the position to 0 to indicate
  // a directional light source

  GLfloat position[4] = { 30,30,100, 1};
  GLfloat diffuse[4] = { 0.75,0.75,0.75,1};
  GLfloat specular[4] = { 0,0,0,1};
  GLfloat ambient[4] = { 0.2, 0.2, 0.2, 1.0 };

  GLfloat zero[4] = {0,0,0,0};
  glLightfv(GL_LIGHT1, GL_POSITION, position);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT1, GL_SPECULAR, specular);
  glLightfv(GL_LIGHT1, GL_AMBIENT, zero);
  glEnable(GL_LIGHT1);
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
  glEnable(GL_COLOR_MATERIAL);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);

  GLfloat spec_mat[4] = {1,1,1,1};
  float glexponent = 30;
  glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, &glexponent);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, spec_mat);

  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
  float back_color[] = { 0.0,0.0,1.0,1};
  glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, back_color);
  glEnable(GL_LIGHT1);
}


void GLCanvas::display(void)
{
  // Clear the display buffer, set it to the background color
  glClearColor(1,1,1,0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Set the camera parameters
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  InitLight(); // light will be a headlamp!
  camera->glPlaceCamera();

  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
  
  glCallList(display_list_index);
   
  // Swap the back buffer with the front buffer to display
  // the scene
  glutSwapBuffers();
}

// ========================================================
// Callback function for window resize
// ========================================================

void GLCanvas::reshape(int w, int h) {
  // Set the OpenGL viewport to fill the entire window
  glViewport(0, 0, (GLsizei)w, (GLsizei)h);

  // Set the camera parameters to reflect the changes
  camera->glInit(w, h);

  args->width = w;
  args->height = h;
}

// ========================================================
// Callback function for mouse click or release
// ========================================================

void GLCanvas::mouse(int button, int state, int x, int y) {
  // Save the current state of the mouse.  This will be
  // used by the 'motion' function
  mouseButton = button;
  mouseX = x;
  mouseY = y;
  controlPressed = glutGetModifiers() & GLUT_ACTIVE_CTRL;
}

// ========================================================
// Callback function for mouse drag
// ========================================================

void GLCanvas::motion(int x, int y) {
if (mouseButton == GLUT_LEFT_BUTTON) {
    //camera->rotateCamera(0.005*(mouseX-x), 0.005*(mouseY-y));
    mouseX = x;
    mouseY = y;
    GLCanvas::curmouseX = x;
    GLCanvas::curmouseY = y;
	
	float tempx = GLCanvas::curmouseX * (2.0 / 500.0) - 1.0;
    float tempy = GLCanvas::curmouseY * (2.0 / 500.0) - 1.0;
    float xx=tempx+0.1f; 
	float yy=-tempy;
	//std::cout<<"X: "<<x<<" Y: "
	//std::cout<<"xx: "<<xx<<" yy: "<<yy<<endl;
	checkBoundingBoxes(xx,yy);
	//GLCanvas::lastX=xx;
	//GLCanvas::lastY=yy;
	
  }
  // Middle button = translation
  // (move camera perpendicular to the direction vector)
  else if (mouseButton == GLUT_MIDDLE_BUTTON) {
    //camera->truckCamera((mouseX-x)*0.5, (y-mouseY)*0.5);
    camera->rotateCamera(0.005*(mouseX-x), 0.005*(mouseY-y));
    mouseX = x;
    mouseY = y;
    GLCanvas::curmouseX = x;
    GLCanvas::curmouseY = y;
    //std::cout << "middle button!!" << std::endl;
    //[CUBE_TEXTURE] ); }

  }
  // Right button = dolly or zoom
  // (move camera along the direction vector)
  else if (mouseButton == GLUT_RIGHT_BUTTON) {
    if (controlPressed) {
      camera->zoomCamera(mouseY-y);
    } else {
      camera->dollyCamera(mouseY-y);
    }
    mouseX = x;
    mouseY = y;
    GLCanvas::curmouseX = x;
    GLCanvas::curmouseY = y;
  }
  
    if (args->animate) {
    // do 10 steps of animation before rendering
    for (int i = 0; i < 10; i++) {
      if (mesh) mesh->Animate();
      }
    }
  // Redraw the scene with the new camera parameters
  display();
  //glutPostRedisplay();

/*
  // Left button = rotation
  // (rotate camera around the up and horizontal vectors)
  if (mouseButton == GLUT_LEFT_BUTTON) {
    camera->rotateCamera(0.005*(mouseX-x), 0.005*(mouseY-y));
    mouseX = x;
    mouseY = y;
  }
  // Middle button = translation
  // (move camera perpendicular to the direction vector)
  else if (mouseButton == GLUT_MIDDLE_BUTTON) {
    camera->truckCamera(mouseX-x, y-mouseY);
    mouseX = x;
    mouseY = y;
  }
  // Right button = dolly or zoom
  // (move camera along the direction vector)
  else if (mouseButton == GLUT_RIGHT_BUTTON) {
    if (controlPressed) {
      camera->zoomCamera(mouseY-y);
    } else {
      camera->dollyCamera(mouseY-y);
    }
    mouseX = x;
    mouseY = y;
  }

  // Redraw the scene with the new camera parameters
  glutPostRedisplay();
  */
}

// ========================================================
// Callback function for keyboard events
// ========================================================

void GLCanvas::keyboard(unsigned char key, int x, int y) {
  switch (key) {
  case 'w':  case 'W':
    args->wireframe = !args->wireframe;
    Render();
    break;
  case 'q':  case 'Q':
    delete mesh;
    delete args;
    args = NULL;
    delete camera;
    camera = NULL;
    printf ("program exiting\n");
    exit(0);
    break;
  case 'a': case 'A':
    // toggle continuous animation
    args->animate = !args->animate;
    if (args->animate) 
      printf ("animation started, press 'A' to stop\n");
    else
      printf ("animation stopped, press 'A' to start\n");
    break;
  case ' ':
    // a single step of animation
    mesh->Animate();
     display();//Render();
    break; 
  case 'm':  case 'M': 
    args->particles = !args->particles;
    display();//Render();
    break; 
  case 's':  case 'S': 
    args->surface = !args->surface;
    display();//Render();
    break; 
  case 'v':  case 'V': 
    args->velocity = !args->velocity;
    display();//Render();
    break; 
  case 'f':  case 'F': 
    args->force = !args->force;
    display();//Render();
    break; 
  case 'r':  case 'R': 
    // reset system
    //Load();
    display();//Render();
    break; 
    
  default:
    printf("UNKNOWN KEYBOARD INPUT  '%c'\n", key);
  }
}

// ========================================================
// Initialize all appropriate OpenGL variables, set
// callback functions, and start the main event loop.
// This function will not return but can be terminated
// by calling 'exit(0)'
// ========================================================

void GLCanvas::initialize(ArgParser *_args, Mesh *_mesh) {

  args = _args;
  mesh = _mesh;
  Vec3f camera_position = Vec3f(0,0,5);
  Vec3f point_of_interest = Vec3f(0,0,0);
  Vec3f up = Vec3f(0,1,0);
  camera = new PerspectiveCamera(camera_position, point_of_interest, up, 20 * M_PI/180.0);

  // setup glut stuff
  glutInitWindowSize(args->width, args->height);
  glutInitWindowPosition(100,100);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);
  glutCreateWindow("OpenGL Viewer");

  // basic rendering 
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glShadeModel(GL_SMOOTH);
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
  GLfloat ambient[] = { 0.2, 0.2, 0.2, 1.0 };
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
  glCullFace(GL_BACK);
  glDisable(GL_CULL_FACE);

  display_list_index = glGenLists(1);
  
  /////////////////////////////////// TEXTURE
  
   glEnable ( GL_COLOR_MATERIAL );
   glColorMaterial ( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );

	glEnable ( GL_TEXTURE_2D );
   glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );
   glGenTextures (3, texture_id);

   image_t   temp_image;

   glBindTexture ( GL_TEXTURE_2D, texture_id[0]);//[CUBE_TEXTURE] );
   tgaLoad  ( "scalpel1.tga", &temp_image, TGA_FREE );//| TGA_LOW_QUALITY );
      glBindTexture ( GL_TEXTURE_2D, texture_id[1]);//[CUBE_TEXTURE] );
   tgaLoad  ( "scalpel2.tga", &temp_image, TGA_FREE );//| TGA_LOW_QUALITY );
         glBindTexture ( GL_TEXTURE_2D, texture_id[2]);//[CUBE_TEXTURE] );
   tgaLoad  ( "cloth.tga", &temp_image, TGA_FREE );//| TGA_LOW_QUALITY );
   //glEnable ( GL_CULL_FACE );
   glBindTexture (GL_TEXTURE_2D, texture_id[0]);
   /////////////////////////////////////////////////////

  // Initialize callback functions
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);

  Render();

  // Enter the main rendering loop
  glutMainLoop();
}

void GLCanvas::SmoothMotion(int x, int y)
{
     curmouseX = x;
     curmouseY = y;
     display(); 
     //glutPostRedisplay();
}

void GLCanvas::cutCloth(ClothParticle& cp, int i, int j, bool topEdge, bool bottomEdge, bool leftEdge, bool rightEdge)
{
	vector<pair<int, int> >::iterator p;
	//delete all struc connections to this vertex
	for(unsigned int k=0; k<mesh->getParticle(i,j).struc.size(); k++)
	{
		//meshParticle cp2=mesh->getParticle(mesh->getParticle(i,j).struc[k].first,mesh->getParticle(i,j).struc[k].second);
		p=find(mesh->getParticle(mesh->getParticle(i,j).struc[k].first,mesh->getParticle(i,j).struc[k].second).struc.begin(), \
		 mesh->getParticle(mesh->getParticle(i,j).struc[k].first,mesh->getParticle(i,j).struc[k].second).struc.end(),make_pair(i,j));
		 
		if(p!=mesh->getParticle(mesh->getParticle(i,j).struc[k].first,mesh->getParticle(i,j).struc[k].second).struc.end())
		{
			mesh->getParticle(mesh->getParticle(i,j).struc[k].first,mesh->getParticle(i,j).struc[k].second).struc.erase(p);//cp2.struc.erase(p);
		}
		
	}
	//mesh->getParticle(i,j).struc.clear();//cp.struc.clear();
	
	for(unsigned int k=0; k<mesh->getParticle(i,j).shear.size(); k++)
	{
		//meshParticle cp2=mesh->getParticle(mesh->getParticle(i,j).shear[k].first,mesh->getParticle(i,j).shear[k].second);
		p=find(mesh->getParticle(mesh->getParticle(i,j).shear[k].first,mesh->getParticle(i,j).shear[k].second).shear.begin(), \
		 mesh->getParticle(mesh->getParticle(i,j).shear[k].first,mesh->getParticle(i,j).shear[k].second).shear.end(),make_pair(i,j));
		 
		if(p!=mesh->getParticle(mesh->getParticle(i,j).shear[k].first,mesh->getParticle(i,j).shear[k].second).shear.end())
		{
			mesh->getParticle(mesh->getParticle(i,j).shear[k].first,mesh->getParticle(i,j).shear[k].second).shear.erase(p);//cp2.shear.erase(p);
		}
	}
	//mesh->getParticle(i,j).shear.clear();//cp.shear.clear();
	
	for(unsigned int k=0; k<mesh->getParticle(i,j).bend.size(); k++)
	{
		//meshParticle cp2=mesh->getParticle(mesh->getParticle(i,j).bend[k].first,mesh->getParticle(i,j).bend[k].second);
		p=find(mesh->getParticle(mesh->getParticle(i,j).bend[k].first,mesh->getParticle(i,j).bend[k].second).bend.begin(), \
		 mesh->getParticle(mesh->getParticle(i,j).bend[k].first,mesh->getParticle(i,j).bend[k].second).bend.end(),make_pair(i,j));
		 
		if(p!=mesh->getParticle(mesh->getParticle(i,j).bend[k].first,mesh->getParticle(i,j).bend[k].second).bend.end())
		{
			mesh->getParticle(mesh->getParticle(i,j).bend[k].first,mesh->getParticle(i,j).bend[k].second).bend.erase(p);//cp2.bend.erase(p);
		}
	}
	//mesh->getParticle(i,j).bend.clear();//mesh->getParticle(i,j).bend.clear();
	
	
}

void GLCanvas::checkBoundingBoxes(float x,float y)
{
	//cout<<"x: "<<x<<" y: "<<y<<endl;
	for(unsigned int i=0; i<mesh->nx; i++)
	{
		for(unsigned int j=0; j<mesh->ny; j++)
		{
			if(mesh->getParticle(i,j).box.checkInside(x,y))
			{
				cout<<"Blade is touching mesh particle i:"<<i<<" j: "<<j<<endl;
				
				Vec3f XY = Vec3f(x,y,0);
				ClothParticle cp = mesh->getParticle(i,j);
				
				float distA=cp.box.dist(cp.box.getTopLeft(), XY);
				float distB=cp.box.dist(cp.box.getTopRight(), XY);
				float distC=cp.box.dist(cp.box.getBottomLeft(), XY);
				float distD=cp.box.dist(cp.box.getBottomRight(), XY);
			
				/*cout<<"distA: "<<distA<<endl;
				cout<<"distB: "<<distB<<endl;
				cout<<"distC: "<<distC<<endl;
				cout<<"distD: "<<distD<<endl;*/
			
				float dist=distA;
				int counter=0;
				int k=i, l=j;
				if(dist>distB) { dist=distB; counter=1; }
				if(dist>distC) { dist=distC; counter=2; }
				if(dist>distD) { dist=distD; counter=3; }
				
				
				if(counter==1 && i+1 < mesh->nx) { cp=mesh->getParticle(i+1,j); k=i+1; }
				if(counter==2 && j+1 < mesh->ny) { cp=mesh->getParticle(i,j+1); l=j+1; }
				if(counter==3 && i+1 < mesh->nx && j+1 <mesh->ny) { cp=mesh->getParticle(i+1,j+1); k=i+1; l=j+1; }
				
				if(cp.cut && !cp.cut2)
				{
					bool top=false; bool bottom=false; bool left=false; bool right=false;
					float yDiff = mesh->getParticle(i,j).lastSpot.y() - XY.y();
					float xDiff = mesh->getParticle(i,j).lastSpot.x() - XY.x();
					if(abs(yDiff) >= abs(xDiff)) //vertical
					{
						if(mesh->getParticle(i,j).lastSpot.y() > XY.y()) //going down
						{
							if(counter == 0 || counter == 2) { left=true; }
							else { right=true; }
						}
						else //going up
						{
							if(counter == 0 || counter == 2) { left=true; }
							else{ right=true; }
						}
					}
					else //horizontal
					{
						if(mesh->getParticle(i,j).lastSpot.x() > XY.x()) //going left
						{	
							if(counter < 2) { top=true; }
							else { bottom=true; }
						}
						else //going right
						{
							if(counter < 2) { top=true; }
							else{ bottom=true; }
						}
					}
					
					cout<<" top: "<<top<<" bottom: "<<bottom<<" left: "<<left<<" right: "<<right<<endl;
					
					Vec3f cutForce=Vec3f(XY.x() - mesh->getParticle(i,j).lastSpot.x(),XY.y() - mesh->getParticle(i,j).lastSpot.y(),0.0);
					cutForce.Normalize();
					cutForce=cutForce*10.0;
					
					//cout<<cp.getVelocity()<<endl;
					mesh->getParticle(i,j).setVelocity(mesh->getParticle(i,j).getVelocity()+cutForce);
					cutCloth(cp,k,l,top, bottom, left, right);
					mesh->getParticle(i,j).cut2=true;
					//cout<<cp.getVelocity()<<endl;
				}	
				
				if(!cp.cut)
				{
					mesh->getParticle(i,j).cut=true;
					mesh->getParticle(i,j).lastSpot=XY;
				}
				
			}	
			
		}
	}
}


void GLCanvas::Render() {
  HandleGLError();
  glNewList(display_list_index, GL_COMPILE_AND_EXECUTE);
  // =========================================================
  // put your GL drawing calls inside the display list for efficiency
  mesh->Paint(args);
  // =========================================================
  glEndList();
  glutPostRedisplay();
  HandleGLError();
}

// ========================================================
// ========================================================


int HandleGLError() {
  GLenum error;
  int i = 0;
  while ((error = glGetError()) != GL_NO_ERROR) {
    printf ("GL ERROR(%d):  %s\n", i, gluErrorString(error));
    i++;
  }
  if (i == 0) return 1;
  return 0;
}

